package com.zluyuer.ermu.msg;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.dom4j.Element;

public abstract class SmartMessage<T> extends Message {

    private static Map<String, String> typeNames = new HashMap<String, String>();
    private static Set<String> ignoredAttrs = new HashSet<String>();
    
    static {
        typeNames.put("int", "int");
        typeNames.put("Integer", "int");
        typeNames.put("double", "double");
        typeNames.put("Double", "double");
        typeNames.put("float", "float");
        typeNames.put("Float", "float");
        typeNames.put("List", "list");
        typeNames.put("String", "string");
        typeNames.put("Map", "map");
        
        ignoredAttrs.add("id");
        ignoredAttrs.add("code");
        ignoredAttrs.add("msgCode");
        ignoredAttrs.add("clazz");
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void buildBody(Element body) throws Exception {
        BeanInfo bi = Introspector.getBeanInfo(getClass());
        PropertyDescriptor[] pds = bi.getPropertyDescriptors();
        
        for (PropertyDescriptor pd : pds) {
             String pName = pd.getName();
             String pTypeName = pd.getPropertyType().getSimpleName();
             Method getMethod = pd.getReadMethod();
             
             if (pTypeName.equals("Class") || ignoredAttrs.contains(pName))
                 continue;
             
             if (pTypeName.equals("List")) {
                 // List with simple data type attribute 
                 List list = (List)getMethod.invoke(this);
                 if (list.size() == 0)
                     continue;
                 
                 Element e = body.addElement(pName);
                 e.addAttribute("type", "list");
                 String typeName = list.get(0).getClass().getSimpleName();
                 typeName = typeNames.get(typeName);
                 e.addAttribute("dtype", typeName);
                 for (Object o : list)
                     e.addElement("item").addText(String.valueOf(o));
                 
             } else if (pTypeName.equals("Map")) {
                 Map map = (Map)getMethod.invoke(this);
                 if (map.size() == 0)
                     continue;
                 
                 Element e = body.addElement(pName);
                 e.addAttribute("type", "map");
                 Set keys = map.keySet();
                 Iterator i = keys.iterator();
                 Object ko = i.next();
                 Object vo = map.get(ko);
                 String ktype = ko.getClass().getSimpleName();
                 ktype = typeNames.get(ktype);
                 String vtype = vo.getClass().getSimpleName();
                 vtype = typeNames.get(vtype);
                 e.addAttribute("ktype", ktype);
                 e.addAttribute("vtype", vtype);
                 
                 for (Object key : keys) {
                     Object value = map.get(key);
                     e.addElement(String.valueOf(key)).addText(String.valueOf(value));
                 }
                 
             } else {
                 // Simple data type attribute 
                 String value = String.valueOf(getMethod.invoke(this));
                 String typeName = typeNames.get(pTypeName);
                 body.addElement(pName).addText(value).addAttribute("type", typeName);
             }
         }
    }

    /**
     * We suppose that at most 1 level of list elements.
     */
    @SuppressWarnings("unchecked")
    @Override
    protected void parseBody(Element body) throws Exception {
        
        for (Iterator i = body.elementIterator(); i.hasNext();) {
            Element e = (Element)i.next();
            String value = e.getText();
            String pName = e.getName();
            String pTypeName = e.attributeValue("type");
            if (pTypeName == null || pTypeName.equals(""))
                continue;
            
            if (pTypeName.equals("int")) {
                PropertyUtils.setProperty(this, pName, Integer.valueOf(value));
                
            } else if (pTypeName.equals("float")) {
                PropertyUtils.setProperty(this, pName, Float.valueOf(value));
                
            } else if (pTypeName.equals("double")) {
                PropertyUtils.setProperty(this, pName, Double.valueOf(value));
                
            } else if (pTypeName.equals("string")) {
                PropertyUtils.setProperty(this, pName, value);
                
            } else if (pTypeName.equals("list")) {
                String dataType = e.attributeValue("dtype");
                int size = e.elements().size();
                String[] texts = new String[size];
                int count = 0;
                for (Iterator j = e.elementIterator(); j.hasNext();) {
                    Element item = (Element)j.next();
                    texts[count++] = item.getText();
                }
                
                if (dataType.equals("string")) {
                    List<String> list = new ArrayList<String>();
                    for (String text : texts)
                        list.add(text);
                    PropertyUtils.setProperty(this, pName, list);
                } else if (dataType.equals("int")) {
                    List<Integer> list = new ArrayList<Integer>();
                    for (String text : texts)
                        list.add(Integer.valueOf(text));
                    PropertyUtils.setProperty(this, pName, list);
                } else if (dataType.equals("float")) {
                    List<Float> list = new ArrayList<Float>();
                    for (String text : texts)
                        list.add(Float.valueOf(text));
                    PropertyUtils.setProperty(this, pName, list);
                } else if (dataType.equals("double")) {
                    List<Double> list = new ArrayList<Double>();
                    for (String text : texts)
                        list.add(Double.valueOf(text));
                    PropertyUtils.setProperty(this, pName, list);
                }
                
            } else if (pTypeName.equals("map")) {
                String kDataType = e.attributeValue("ktype");
                String vDataType = e.attributeValue("vtype");
                int size = e.elements().size();
                String[][] texts = new String[size][];
                int count = 0;
                for (Iterator j = e.elementIterator(); j.hasNext();) {
                    Element ele = (Element)j.next();
                    String[] pair = new String[2];
                    pair[0] = ele.getName();
                    pair[1] = ele.getText();
                    texts[count++] = pair;
                }
                
                if (kDataType.equals("string") && vDataType.equals("int")) {
                    Map<String, Integer> map = new HashMap<String, Integer>();
                    for (String[] pair : texts) 
                        map.put(pair[0], Integer.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("string") && vDataType.equals("float")) {
                    Map<String, Float> map = new HashMap<String, Float>();
                    for (String[] pair : texts) 
                        map.put(pair[0], Float.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("string") && vDataType.equals("double")) {
                    Map<String, Double> map = new HashMap<String, Double>();
                    for (String[] pair : texts) 
                        map.put(pair[0], Double.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("string") && vDataType.equals("string")) {
                    Map<String, String> map = new HashMap<String, String>();
                    for (String[] pair : texts) 
                        map.put(pair[0], pair[1]);
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("int") && vDataType.equals("int")) {
                    Map<Integer, Integer> map = new HashMap<Integer, Integer>();
                    for (String[] pair : texts) 
                        map.put(Integer.valueOf(pair[0]), Integer.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("int") && vDataType.equals("float")) {
                    Map<Integer, Float> map = new HashMap<Integer, Float>();
                    for (String[] pair : texts) 
                        map.put(Integer.valueOf(pair[0]), Float.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("int") && vDataType.equals("double")) {
                    Map<Integer, Double> map = new HashMap<Integer, Double>();
                    for (String[] pair : texts) 
                        map.put(Integer.valueOf(pair[0]), Double.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("int") && vDataType.equals("string")) {
                    Map<Integer, String> map = new HashMap<Integer, String>();
                    for (String[] pair : texts) 
                        map.put(Integer.valueOf(pair[0]), pair[1]);
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("float") && vDataType.equals("int")) {
                    Map<Float, Integer> map = new HashMap<Float, Integer>();
                    for (String[] pair : texts) 
                        map.put(Float.valueOf(pair[0]), Integer.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("float") && vDataType.equals("float")) {
                    Map<Float, Float> map = new HashMap<Float, Float>();
                    for (String[] pair : texts) 
                        map.put(Float.valueOf(pair[0]), Float.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("float") && vDataType.equals("double")) {
                    Map<Float, Double> map = new HashMap<Float, Double>();
                    for (String[] pair : texts) 
                        map.put(Float.valueOf(pair[0]), Double.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("float") && vDataType.equals("string")) {
                    Map<Float, String> map = new HashMap<Float, String>();
                    for (String[] pair : texts) 
                        map.put(Float.valueOf(pair[0]), pair[1]);
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("double") && vDataType.equals("int")) {
                    Map<Double, Integer> map = new HashMap<Double, Integer>();
                    for (String[] pair : texts) 
                        map.put(Double.valueOf(pair[0]), Integer.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("double") && vDataType.equals("float")) {
                    Map<Double, Float> map = new HashMap<Double, Float>();
                    for (String[] pair : texts) 
                        map.put(Double.valueOf(pair[0]), Float.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("double") && vDataType.equals("double")) {
                    Map<Double, Double> map = new HashMap<Double, Double>();
                    for (String[] pair : texts) 
                        map.put(Double.valueOf(pair[0]), Double.valueOf(pair[1]));
                    PropertyUtils.setProperty(this, pName, map);
                    
                } else if (kDataType.equals("double") && vDataType.equals("string")) {
                    Map<Double, String> map = new HashMap<Double, String>();
                    for (String[] pair : texts) 
                        map.put(Double.valueOf(pair[0]), pair[1]);
                    PropertyUtils.setProperty(this, pName, map);
                }
            }
        }
    }
}
